package fiberprometheus

import (
	"strconv"
	"time"

	"github.com/gofiber/fiber"
	"github.com/prometheus/client_golang/prometheus"
	"github.com/prometheus/client_golang/prometheus/promauto"
	"github.com/prometheus/client_golang/prometheus/promhttp"
	"github.com/valyala/fasthttp/fasthttpadaptor"
)

type Middleware struct {
	Namespace         string
	Subsystem         string
	MetricPath        string
	reqCount          *prometheus.CounterVec
	reqDuration       *prometheus.HistogramVec
	reqDurationSummay *prometheus.SummaryVec
}

func (m *Middleware) PrometheusHandler() fiber.Handler {
	return func(c *fiber.Ctx) error {
		if c.Path() == m.MetricPath {
			c.Next()
			return nil
		}

		start := time.Now()

		c.Next()

		r := c.Route()

		statusCode := strconv.Itoa(c.Response().StatusCode())
		elapsed := float64(time.Since(start)) / float64(time.Second)
		method := c.Method()
		uri := r.Path
		m.reqCount.WithLabelValues(method, uri, statusCode).Inc()
		m.reqDuration.WithLabelValues(method, uri).Observe(elapsed)
		m.reqDurationSummay.WithLabelValues(method, uri).Observe(elapsed)
		return nil
	}
}

func (m *Middleware) Register(app fiber.Router) {
	m.registerDefaultMetrics()
	m.SetupPath(app)
	app.Use(m.PrometheusHandler())
}

func (m *Middleware) SetupPath(app fiber.Router) {
	app.Get(m.MetricPath, m.metricHandler)
}

func (m *Middleware) metricHandler(c *fiber.Ctx) error {
	p := fasthttpadaptor.NewFastHTTPHandler(promhttp.Handler())
	p(c.Context())
	return nil
}

func (m *Middleware) registerDefaultMetrics() {
	m.reqCount = promauto.NewCounterVec(
		prometheus.CounterOpts{
			Name:      "requests_total",
			Namespace: m.Namespace,
			Subsystem: m.Subsystem,
			Help:      "Number of HTTP requests",
		},
		[]string{"method", "path", "status_code"},
	)

	m.reqDuration = promauto.NewHistogramVec(
		prometheus.HistogramOpts{
			Name:      "request_duration_seconds",
			Namespace: m.Namespace,
			Subsystem: m.Subsystem,
			Buckets:   []float64{0.001, 0.01, 0.1, 0.25, 0.5, 1, 2.5, 5, 10},
			Help:      "Duration of HTTP requests",
		},
		[]string{"method", "path"},
	)

	m.reqDurationSummay = prometheus.NewSummaryVec(
		prometheus.SummaryOpts{
			Name:      "http_request_duration_summary",
			Namespace: m.Namespace,
			Subsystem: m.Subsystem,
			Help:      "A summary of the HTTP request durations in seconds.",
			Objectives: map[float64]float64{
				0.5:  0.05,  // 第50个百分位数，最大绝对误差为0.05。
				0.9:  0.01,  // 第90个百分位数，最大绝对误差为0.01。
				0.99: 0.001, // 第90个百分位数，最大绝对误差为0.001。
			},
		},
		[]string{"method", "path"},
	)
}

func NewMiddleware(namespace string, subsystem string, metricPath string) *Middleware {
	return &Middleware{
		Namespace:  namespace,
		Subsystem:  subsystem,
		MetricPath: metricPath,
	}
}
